﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace ExpressionEvaluator
{
	/// <summary>
	/// Code parser exception.
	/// </summary>
	/// <remarks>
	/// Exception is throw, when code is not well formed.
	/// </remarks>
	public class CodeParserException : Exception
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="CodeParserException"/> class.
		/// </summary>
		/// <param name="code">The code.</param>
		public CodeParserException(string code)
			: base(string.Format("Fails to tokenize code:\n{0}", code))
		{
		}
	}

	internal static class EvalParser
	{
		private const string cTextRegexPart = @"""((?:(?:\\\\)|(?:\\"")|[^""])*)""";
		private const string cExecutableRegexPart = @"((?:\d+\.\d+\w?)|(?:\<\>)?\w+(?:\`\d)?)";
        private const string cDelimiterRegexPart = @"([\(\)\[\]\.,\{\}])";
		private const string cOperatorRegexPart = @"([\-]|[^\w\(\)\[\]\-\.,\s\\""]+)";
		private const string cCodeParserRegex = cTextRegexPart + "|" + cExecutableRegexPart + "|" + cDelimiterRegexPart + "|" + cOperatorRegexPart;
		/// <summary>
		/// Parses the code.
		/// </summary>
		public static CodeBlockCollection ParseCode(string code)
		{
			MatchCollection matches = Regex.Matches(code, cCodeParserRegex, RegexOptions.Singleline | RegexOptions.Compiled);
			if ((matches == null) || (matches.Count == 0))
			{
				throw new CodeParserException(code);
			}
			CodeBlockCollection result = new CodeBlockCollection(code);
			for (int i = 0; i < matches.Count; ++i)
			{
				Match match = matches[i];
				if (match.Success)
				{
					int groupIndex = FindNotEmptyGroupIndex(match);
					CodeBlock block = new CodeBlock(match.Groups[groupIndex].Value, (CodeTypeEnum)groupIndex, match.Index, match.Length);
					result.Add(block);
				}
			}
			return result;
		}

		/// <summary>
		/// Finds the index of not empty group.
		/// </summary>
		/// <param name="match">The match.</param>
		/// <returns>Position index of current code block in match groups.</returns>
		/// <remarks>
		/// One of group items should always be not empty string!!
		/// </remarks>
		private static int FindNotEmptyGroupIndex(Match match)
		{
			for (int i = 1; i < match.Groups.Count; ++i)
			{
				if (match.Groups[i].Value != string.Empty)
				{
					return i;
				}
			}
			// if empty then it means it is empty text
			return 1;
		}
	}
}
